/* drivers/rtc/rtc-goldfish.c
**
** Copyright (C) 2007 Google, Inc.
**
** This software is licensed under the terms of the GNU General Public
** License version 2, as published by the Free Software Foundation, and
** may be copied, distributed, and modified under those terms.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
*/

#include <asm-arm/irq.h>
#include <goldfish/dma.h>
#include <asm-arm/io.h>
#include <goldfish/head.h>		
#include <goldfish/types.h>		
#include <asm-arm/errno-base.h>
#include <asm-arm/arch-goldfish/hardware.h>
#include <asm-arm/atomic.h>
#include <asm-arm/div64.h>

enum {
	TIMER_TIME_LOW          = 0x00, // get low bits of current time and update TIMER_TIME_HIGH
	TIMER_TIME_HIGH         = 0x04, // get high bits of time at last TIMER_TIME_LOW read
	TIMER_ALARM_LOW         = 0x08, // set low bits of alarm and activate it
	TIMER_ALARM_HIGH        = 0x0c, // set high bits of next alarm
	TIMER_CLEAR_INTERRUPT   = 0x10,
	TIMER_CLEAR_ALARM       = 0x14
};

/* interrupt flags */
#define RTC_IRQF 0x80 /* any of the following is active */
#define RTC_PF 0x40
#define RTC_AF 0x20
#define RTC_UF 0x10


/* Parameters used to convert the timespec values: */
#define MSEC_PER_SEC	1000L
#define USEC_PER_MSEC	1000L
#define NSEC_PER_USEC	1000L
#define NSEC_PER_MSEC	1000000L
#define USEC_PER_SEC	1000000L
#define NSEC_PER_SEC	1000000000L
#define FSEC_PER_SEC	1000000000000000L
	
/*
 * The struct used to pass data via the following ioctl. Similar to the
 * struct tm in <time.h>, but it needs to be here so that the kernel
 * source is self contained, allowing cross-compiles, etc. etc.
 */




struct goldfish_rtc {
	uint32_t base;
	uint32_t irq;
	//struct rtc_device *rtc;
};
static struct goldfish_rtc	*qrtc;
#define LEAPS_THRU_END_OF(y) ((y)/4 - (y)/100 + (y)/400)
#define LEAP_YEAR(year) ((!(year % 4) && (year % 100)) || !(year % 400))

static const unsigned char rtc_days_in_month[] = {
	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};

/*
 * The number of days in the month.
 */
int rtc_month_days(unsigned int month, unsigned int year)
{
	return rtc_days_in_month[month] + (LEAP_YEAR(year) && month == 1);
}
	
/*
 * Convert seconds since 01-01-1970 00:00:00 to Gregorian date.
 */
void rtc_time_to_tm(unsigned long time, struct rtc_time *tm)
{
	unsigned int month, year;
	int days;

	days = time / 86400;
	time -= (unsigned int) days * 86400;

	/* day of the week, 1970-01-01 was a Thursday */
	tm->tm_wday = (days + 4) % 7;

	year = 1970 + days / 365;
	days -= (year - 1970) * 365
		+ LEAPS_THRU_END_OF(year - 1)
		- LEAPS_THRU_END_OF(1970 - 1);
	if (days < 0) {
		year -= 1;
		days += 365 + LEAP_YEAR(year);
	}
	tm->tm_year = year - 1900;
	tm->tm_yday = days + 1;

	for (month = 0; month < 11; month++) {
		int newdays;

		newdays = days - rtc_month_days(month, year);
		if (newdays < 0)
			break;
		days = newdays;
	}
	tm->tm_mon = month;
	tm->tm_mday = days + 1;

	tm->tm_hour = time / 3600;
	time -= tm->tm_hour * 3600;
	tm->tm_min = time / 60;
	tm->tm_sec = time - tm->tm_min * 60;
}

static irqreturn_t
goldfish_rtc_interrupt(int irq, void *dev_id)
{
	struct goldfish_rtc	*qrtc = dev_id;
	unsigned long		events = 0;

	__raw_writel(1, qrtc->base + TIMER_CLEAR_INTERRUPT);
	events = RTC_IRQF | RTC_AF;

	//rtc_update_irq(qrtc->rtc, 1, events);

	return IRQ_HANDLED;
}

int goldfish_rtc_read_time(/*struct device *dev, */struct rtc_time *tm)
{
	int64_t time;
	//struct goldfish_rtc	*qrtc = platform_get_drvdata(to_platform_device(dev));

	time = __raw_readl(qrtc->base + TIMER_TIME_LOW);
	time |= (int64_t)__raw_readl(qrtc->base + TIMER_TIME_HIGH) << 32;
	do_div(time, NSEC_PER_SEC);

	rtc_time_to_tm(time, tm);
	return 0;
}


static int goldfish_rtc_probe(struct platform_device *pdev)
{
	int ret;
	struct resource *r;
	//struct goldfish_rtc *qrtc;

	qrtc = xmalloc(struct goldfish_rtc);
	memset(qrtc,0,sizeof(struct goldfish_rtc));
	if(qrtc == NULL) {
		ret = -ENOMEM;
		goto err_qrtc_alloc_failed;
	}
	//platform_set_drvdata(pdev, qrtc);

	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if(r == NULL) {
		ret = -ENODEV;
		goto err_no_io_base;
	}
	qrtc->base = IO_ADDRESS(r->start - IO_START);
	qrtc->irq = platform_get_irq(pdev, 0);
	if(qrtc->irq < 0) {
		ret = -ENODEV;
		goto err_no_irq;
	}
	//qrtc->rtc = rtc_device_register(pdev->name, &pdev->dev,
	//                                &goldfish_rtc_ops, THIS_MODULE);
//	if (IS_ERR(qrtc->rtc)) {
//		ret = PTR_ERR(qrtc->rtc);
//		goto err_rtc_device_register_failed;
//	}

	ret = request_irq(qrtc->irq, goldfish_rtc_interrupt, 0, pdev->name, qrtc);
	if(ret)
		goto request_irq;

	return 0;

	//free_irq(qrtc->irq, qrtc);
request_irq:
	//rtc_device_unregister(qrtc->rtc);
err_rtc_device_register_failed:
err_no_irq:
err_no_io_base:
	xfree(qrtc);
err_qrtc_alloc_failed:
	return ret;
}

//static int goldfish_rtc_remove(struct platform_device *pdev)
//{
//	struct goldfish_rtc	*qrtc = platform_get_drvdata(pdev);
//	free_irq(qrtc->irq, qrtc);
//	rtc_device_unregister(qrtc->rtc);
//	kfree(qrtc);
//	return 0;
//}

struct platform_driver goldfish_timer = {
	.probe = goldfish_rtc_probe,
//	.remove = goldfish_rtc_remove,
//	.driver = {
//		.name = "goldfish_rtc"
//	}
};


